# -*-coding=utf-8-*-
# author: LittleDrinks

import json
import os
import random
import threading
import time
import tkinter as tk
from tkinter import ttk
from tkinter.filedialog import askdirectory, askopenfilename

import pandas as pd
import pyttsx3


# 放置在 Root 上，询问词库路径
# 组件：输入框，浏览，清空输入框，添加询问框，删除询问框
class AskForFileFrame(tk.Frame):
    def __init__(self, row_num, parent, *args):
        self.row_num = row_num
        self.parent = parent
        self.path = tk.StringVar()
        super().__init__(self.parent, *args)

        self.path_Entry = tk.Entry(self, textvariable=self.path)
        self.path_Entry.pack(
            side="left", ipadx=70, ipady=4, padx=3
        )

        self.btn_ask_for_file = tk.Button(
            self, command=self.ask_for_file, text="浏览"
        )
        self.btn_ask_for_file.pack(side="left")

        self.btn_clear = tk.Button(
            self, command=self.clear_entry, text="清空"
        )
        self.btn_clear.pack(side="left")

        self.btn_add = tk.Button(
            self, command=self.create_frame, text="+"
        )
        self.btn_add.pack(side="left")

        if row_num >= 1:
            self.btn_destroy = tk.Button(
                self, command=self.destroy_frame, text="-"
            )
            self.btn_destroy.pack(side="left")

    # 新建询问框，多选词库
    def create_frame(self):
        frame = AskForFileFrame(self.row_num+1, self.parent)
        self.parent.ask_for_file_frame.append(frame)
        self.parent.fresh()

    # 销毁最后一个询问框
    def destroy_frame(self):
        self.parent.ask_for_file_frame.pop().destroy()
        self.parent.fresh()

    # 快速清空输入框
    def clear_entry(self):
        self.path.set("")

    # 浏览
    def ask_for_file(self):
        self.path.set(
            askopenfilename(filetypes=[
                ("CSV UTF-8(逗号分隔)", "*.csv"), ("json", "*.json")
            ])
        )


# 最底层的窗口，输入词库路径
class Root(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("背单词")

        self.word_reciting_ui = None

        self.all_AskForFileFrame = tk.Frame(self)
        self.all_AskForFileFrame.ask_for_file_frame = []
        self.all_AskForFileFrame.fresh = lambda: self.fresh()

        self.all_AskForFileFrame.ask_for_file_frame.append(
            AskForFileFrame(0, self.all_AskForFileFrame)
        )

        self.btn_row = 1
        self.btn = tk.Button(
            self, text="确认", command=self.start_recite
        )

        self.bind("<Return>", lambda _: self.start_recite())

        self.fresh()

        self.mainloop()

    # 刷新窗口的显示，每次删除询问框后使用
    def fresh(self):
        self.all_AskForFileFrame.pack(
            side="top", padx=50, expand=1
        )
        self.btn.pack(side="top", pady=5, expand=1)
        for frame in self.all_AskForFileFrame.ask_for_file_frame:
            frame.pack(side="top", expand=1, fill="both")

    # 确定词库，新建 Ui，开始背诵
    def start_recite(self):
        paths = []
        for i in range(len(self.all_AskForFileFrame.ask_for_file_frame)):
            path = self.all_AskForFileFrame.ask_for_file_frame[i].path.get()
            if os.path.exists(path):
                paths.append(path)
            else:
                print("not exists.")
        if paths != []:
            self.word_reciting_ui = Ui(paths, self)


# 放置在 Ui 上，输入英文以背诵
class AnswerFrame(tk.Frame):
    def __init__(self, parent, *args):
        self.parent = parent
        super().__init__(self.parent, *args)
        self.answer_entrys = []
        self.equal_labels  = []

    # 每次换新单词时调用，刷新显示
    def fresh(self):
        # 删除所有输入框和等号标签
        while self.answer_entrys:
            self.answer_entrys.pop().destroy()
        while self.equal_labels:
            self.equal_labels.pop().destroy()
        # 寻找英文中的等号，根据等号数生成输入框，完成对词义相同的单词词组的考察
        cnt_entry = 1
        # 记录字符数量，调整输入框大小
        cnt = 0
        maxn = 10  # 最小为10字符
        if not self.parent.english == None:  # 单词未抽完
            for s in self.parent.english:
                if s != "/" and s != "=":
                    cnt += 1
                else:
                    maxn = max(maxn, cnt)
                    cnt = 0
                if s == "=":
                    cnt_entry += 1
        # 生成并显示输入框
        for i in range(cnt_entry):
            if i > 0:  # 除了第一个输入框，每个输入框左侧都要生成一个显示 = 的标签
                equal_label = tk.Label(self, text="=")
                self.equal_labels.append(equal_label)
                equal_label.grid(row=0, column=2*i-1)
            entry = tk.Entry(self)
            entry.num = i  # 输入框编号
            # entry.bind("<Key>", self.print_event)
            entry.bind("<Left>", self.left_move_focus)
            entry.bind("<Right>", self.right_move_focus)
            self.answer_entrys.append(entry)
            entry.grid(row=0, column=2*i)
            # 动态调整输入框大小
            maxn = max(maxn, cnt)
            entry.config(width=maxn)
        # 把焦点设置在第一个输入框上
        self.set_entry_focus(self.answer_entrys[0])

    # 遍历所有输入框，返回集合，利用集合无序性和无重复性完成批改
    def get(self):
        answers = []
        for entry in self.answer_entrys:
            answers.append(entry.get())
        return set(answers)

    # 测试用
    def print_event(self, event):
        print(event)

    # 生成新的输入框后设置焦点，方便输入
    def set_entry_focus(self, entry):
        entry.focus_set()

    # 按下左键，将焦点移动到左侧的输入框
    def left_move_focus(self, event):
        if event.widget.num - 1 >= 0:  # 在范围内 可以左移
            self.set_entry_focus(self.answer_entrys[event.widget.num-1])

    # 按下右键，将焦点移动到右侧的输入框
    def right_move_focus(self, event):
        if event.widget.num + 1 < len(self.answer_entrys):  # 在范围内 可以右移
            self.set_entry_focus(self.answer_entrys[event.widget.num+1])

# 列出所有的单词，词义
# column <tuple> 列名
class WordTable(tk.Frame):
    def __init__(self, column, parent, *args):

        super().__init__(parent, *args)
        self.column = column
        self.parent = parent
        self.row_num = 0

        self.table_style = ttk.Style()
        self.table_style.configure(
            "Treeview", rowheight=30, font=("微软雅黑", 12)
        )

        self.table = ttk.Treeview(self, column=column)

        # 设置列名
        for column_name in column:
            self.table.heading(column_name, text=column_name)

        # 绑定滚动条
        self.scr = tk.Scrollbar(self)
        self.table.config(yscrollcommand = self.scr.set)
        self.scr.config(command = self.table.yview)

        # 放置组件
        self.table.pack(side="left", fill="both", expand=1)
        self.scr.pack(side="right", fill="y")


    # texts <list>
    # values <list> <tuple>
    # 分别对应 ttk.Treeview.insert 的 text 和 values 参数
    def reset_text(self, texts, values):

        # 先清空
        if len(self.table.get_children()) > 0:
            self.table.delete(self.table.get_children())
            self.row_num = 0

        # 再设置文本
        for text, value in zip(texts, values):
            self.row_num += 1
            self.table.insert(
                "", self.row_num, text=text, values=value
            )


# 确认词库后新建的窗口，开始背诵
class Ui(tk.Toplevel):
    def __init__(self, paths, parent, *args):

        super().__init__(parent, *args)
        self.paths = paths
        self.parent = parent
        self.isRead = False
        self.speaker = pyttsx3.init()
        self.title("背单词")

        self.layout_1()

        self.mainloop()

    # 列表显示单词
    # 两个按钮分别用于隐藏列，便于自测
    # 一个按钮控制获取音频
    def layout_1(self):

        # 列表
        self.word_table = WordTable(("单词", "词义"), self)
        self.word_table.pack(side="top", fill="both", expand=1)
        self.change_table_content()

        # 按钮：点击后开始背诵
        self.start_btn = tk.Button(
            self, text="开始背诵", command=self.start_reciting)
        self.start_btn.pack(side="top", fill="x")
        self.start_btn.bind("<Return>", self.start_reciting)

    def change_table_content(self):
        # 显示内容：编号-单词-词义
        words       = self.read(self.paths)
        self.texts  = []
        self.values = []
        word_num = 0  # 编号
        for english, chinese in words.items():
            word_num += 1
            self.texts.append(f"{word_num}.")

            # 英文中有 '/' 或 '='
            # 中文中有 '\n'
            # 分割
            cnt_eng_split = 0
            res_eng_split = []
            single_eng    = ''
            cnt_chi_split = 0
            res_chi_split = []
            single_chi    = ''
            for i, s in enumerate(english):
                if s == '/' or s == '=':
                    cnt_eng_split += 1
                    res_eng_split.append(single_eng)
                    single_eng = s
                else:
                    single_eng += s
                    if i == len(english)-1:  res_eng_split.append(single_eng)
            for i, s in enumerate(chinese):
                if s == '\n':
                    cnt_chi_split += 1
                    res_chi_split.append(single_chi)
                    single_chi = ''
                else:
                    single_chi += s
                    if i == len(chinese)-1:  res_chi_split.append(single_chi)

            # 设置文字
            cnt = max(cnt_eng_split, cnt_chi_split)
            # 用 '' 补齐
            if cnt > cnt_eng_split:
                res_eng_split += ['']*(cnt-cnt_eng_split)
            if cnt > cnt_chi_split:
                res_chi_split += ['']*(cnt-cnt_chi_split)
            # 添加
            for i in range(cnt+1):
                if i > 0:  self.texts.append('')
                self.values.append(tuple([res_eng_split[i], res_chi_split[i]]))

        # 应用
        self.word_table.reset_text(self.texts, self.values)

    def start_reciting(self):
        self.layout_1_destroy()
        self.layout_2()

    def layout_1_destroy(self):
        del self.texts
        del self.values
        self.word_table.destroy()
        self.start_btn.destroy()

    def layout_2(self):
        self.ask_frame = tk.Frame(self)
        self.ask_frame.pack(side="top", expand=1, fill="x")

        self.ask_frame.words = self.read(self.paths)
        self.ask_frame.eng_list = list(self.ask_frame.words.keys())
        self.ask_frame.english = str()
        self.ask_frame.chinese = str()

        self.meaning_lbl = tk.Label(self.ask_frame, font=("微软雅黑", 18))
        self.meaning_lbl.pack(side="top")

        self.answer_frame = AnswerFrame(self.ask_frame)
        self.answer_frame.pack(side="top", fill="y", expand=1, padx=20)

        self.confirm_btn = tk.Button(self.ask_frame, text="提交", command=self.check)
        self.confirm_btn.pack(side="top")

        self.result_lbl = tk.Label(
            self.ask_frame, text="结果：暂无", font=("微软雅黑", 12), padx=20
        )
        self.result_lbl.pack(side="top", anchor="ne")

        self.wrong_table = WordTable(("单词", "词义"), self)
        self.wrong_table.reset_text(["错误单词："], [('', '')])
        self.wrong_table.pack(
            side="bottom", expand=1, fill="both", padx=20, pady=5
        )

        self.wrong_index = 1  # 错误序号 e.g.: 1.abandon
        self.wrong_dict = {
            "单词": [], "词义": []
        }

        self.bind("<Return>", self.check)
        self.choose_word()

    def read_word(self, word):
        self.isRead = True
        self.speaker.stop()
        self.speaker.say(word)
        self.speaker.runAndWait()
        self.choose_word()
        self.isRead = False

    def read(self, paths):  # 读取单词清单
        dict_eng_chi = dict()
        for path in paths:
            if path.endswith(".json") == True:
                with open(path, encoding="utf-8") as w:
                    dict_eng_chi.update(json.loads(w.read()))
            elif path.endswith(".csv") == True:
                df = pd.read_csv(path, encoding="utf-8")
                words = df["单词"].tolist()
                means = df["词义"].tolist()
                for word, mean in zip(words, means):
                    dict_eng_chi[word] = mean.replace("\\n", "\n")
        return dict_eng_chi

    def choose_word(self):
        # 随机抽单词
        if not self.ask_frame.eng_list:  # 单词抽完
            self.ask_frame.english = None
            self.meaning_lbl["text"] = "单词已抽完"
            if len(self.wrong_table.table.get_children()) == 1:
                self.wrong_table.table.delete(
                    self.wrong_table.table.get_children()
                )
                self.wrong_table.table.insert(
                    "", 0, text="无错误单词", values=("", "")
                )
            else:
                self.confirm_btn["text"] = "导出错误单词"
                self.confirm_btn["command"] = self.save_wrong_dict_in_csv
        else:  # 单词未抽完：打乱列表取末位并更改中文显示
            random.shuffle(self.ask_frame.eng_list)
            self.ask_frame.english = self.ask_frame.eng_list.pop()
            self.ask_frame.chinese = self.ask_frame.words[self.ask_frame.english]
            self.meaning_lbl["text"] = self.ask_frame.chinese
        # 刷新输入框
        self.answer_frame.fresh()

    def check(self, event=None):
        if not self.isRead:
            # 获取输入
            answer_sets = self.answer_frame.get()
            answer_num  = len(self.answer_frame.answer_entrys)
            isRight = True
            # 判断对错
            if self.ask_frame.english == None:
                return  # 抽完 无需判断
            elif len(answer_sets) != answer_num:  # 输入重复答案，批错
                self.result_lbl["text"] = "结果：错误"
                threading.Thread(
                    target=self.read_word, args=(self.ask_frame.english,)
                ).start()
                isRight = False
                self.add_to_wrong_list(
                    self.ask_frame.chinese, self.ask_frame.english
                )
            else:  # 没有重复答案
                model_answer = ""
                for model_answer in self.ask_frame.english.split("="):
                    # 答案数量正确，只需要保证一个标答对应一个答案
                    # 在答案中找不到标答即判错
                    judge = lambda i, j: True if i in j else False
                    if not True in [judge(answer, set(model_answer.split("/")))
                    for answer in answer_sets]:
                        self.result_lbl["text"] = "结果：错误"
                        threading.Thread(
                            target=self.read_word, args=(self.ask_frame.english,)
                        ).start()
                        isRight = False
                        self.add_to_wrong_list(
                            self.ask_frame.chinese, self.ask_frame.english
                        )
                if isRight:
                    self.result_lbl["text"] = "结果：正确"
                    self.choose_word()

    def add_to_wrong_list(self, chinese, english):  # 错误单词汇总
        self.wrong_dict["单词"].append(english)
        self.wrong_dict["词义"].append(chinese)
        self.wrong_table.table.insert(
            "", self.wrong_index, text=f"{self.wrong_index}.",
            values=(english, chinese)
        )
        self.wrong_index += 1

    def save_wrong_dict_in_csv(self):
        filename = (
            f"{askdirectory()}{os.sep}{time.strftime('%Y-%m-%d-%H-%M-%S')}-错误单词.csv"
        )
        pd.DataFrame(self.wrong_dict).to_csv(filename, index="False")
        while len(self.parent.all_AskForFileFrame.ask_for_file_frame) > 1:
            self.parent.all_AskForFileFrame.destroy_frame()
        self.parent.all_AskForFileFrame.ask_for_file_frame[0].path.set(filename)
        self.destroy()


if __name__ == "__main__":
    root = Root()
